/*

   LMODEM.C -- Little MODEM program 
                                      
   Byte, Nov. 1983, pages 410-28                       
                                                                             
   This program implements the Ward Christensen assembly-language           
   program in C.  It is derived from the cmodem13.c program                 
   by Jack M. Wierda and Roderick W. Hart.  The modes available have         
   been greatly reduced.  The program provides terminal emulation           
   and text capture as well as file send and receive capabilities.          
                                                                             
   Hardware-dependent functions have been isolated from the                 
   majority of the program.                                                 
                                                                             
   Written by David D. Clark                                                
   9-Jan-83                                                                 
  
   Adapted to the KayPro II and Software Toolworks' (C/80 V. 3.0) 
   C compiler by Lee R. Bradley

   To compile and assemble with AS.COM, use the following:
                                                    
   A>C -C2500 -D800 B:LMODEM.ASM=LMODEM.C
   A>AS B:LMODEM.COM=B:LMODEM.ASM                     

   Incorporated dynamic allocation of buffer space.  Used ideas in Clark's 
   version 2. 

   To compile, assemble and link with M80 and L80, use the following commands 

   A>C -C2500 -D800 -M1 B:LMODEM.MAC=LMODEM.C
   A>M80 B:LMODEM.REL=B:LMODEM.MAC             
   A>L80 B:LMODEM,STDLIB,CLIBRARY,B:LMODEM/N/E

*/

#include "printf.h"
#include "printf.c"
#include "scanf.h"
#include "scanf.c"

#define    DOTS            50        /* sector counting dots per line */
#define    SPS             12000     /* loops per second  */
#define    SECSIZ          0x80
#define    DATAMASK        0x7f
#define    BUFSECS         128       /* number of file sectors to buffer */
#define    BLKSIZ          16384     /* size of blocks written (1 extent) */
#define    ERRORMAX        10        /* maximum errors before abort */
#define    RETRYMAX        5         /* maximum retrys before abort */
#define    DIRCTIO         6         /* cpm bdos direct-console io command */
#define    INPUT           0xff      /* direct-console io input */
#define    FALSE           0
#define    TRUE            1         /* lrb */
#define    NULL            0         /* lrb */

/***************************************************************************/
/*                                                                         */
/*   Special characters used in the file transfer-protocol.                */
/*                                                                         */
/***************************************************************************/

#define      TIMEOUT  -1   /* timeout character  */
#define      SOH      1    /* start of sector character */
#define      EOT      4    /* end of transmission character  */
#define      ACK      6    /* acknowledge sector transmission */
#define      NAK      21   /* error in transmission detected */

/****************************************************************************/
/*                                                                          */
/*   Miscellaneous ASCII characters                                         */
/*                                                                          */
/****************************************************************************/

#define    TAB             9
#define    LF             10
#define    CR             13
#define    CTRLZ          26  /* end of text-file character */

/****************************************************************************/
/*                                                                          */
/*    These #defines determine which keys will be interpreted as            */
/*    command characters.                                                   */
/*                                                                          */
/****************************************************************************/

#define      CTRLB   2
#define      CTRLE   5
#define      CTRLK   11
#define      CTRLL   12
#define      CTRLQ   17
#define      CTRLR   18
#define      CTRLS   19
#define      CTRLT   20
#define      CTRLV   22

#define      SETBAUD   CTRLB   /* set baudrate */
#define      CAPTURE   CTRLT   /* toggle text capture */
#define      HELP      CTRLE   /* get instructions */
#define      KEEP      CTRLK   /* keep text buffer  */
#define      LITERAL   CTRLL   /* send literal character */
#define      QUIT      CTRLQ   /* quit */
#define      RECEIVE   CTRLR   /* receive file by sectors  */
#define      SEND      CTRLS   /* send file by sectors */
#define      VIEW      CTRLV   /* view data transfered  */

char ViewMode, BFlag, KbData, ModData;
char AsciiFlg, ShowTrans, ShowRec, View;
char *Bufr;
char FileName[14];
int Fd;
unsigned BufSiz,BufPtr;

char *baudrate;
extern char Cmode;

main(argc, argv)
int argc;
char **argv;
{

   Cmode = 0;
   AsciiFlg = ShowRecv = ShowTrans = BFlag = View = FALSE;
   BufPtr = 0;
   BufSiz=0x7f80;                     /*  make sure this isn't too big !! */
   ViewMode = KbData = NULL;
   if ((Bufr = alloc(BufSiz)) == 0) {
      printf("Can't allocate the buffer\n");
      exit();
      }

   instruct();
   initializemodem();

   while (ctsready() && (KbData != QUIT)) {
      if (KbData = bdos(DIRCTIO, INPUT)) /* get any char at kbd */
         switch (KbData) {

         case SETBAUD:
            printf("\nEnter 1 for 300, 2 for 1200 or 3 for 2400 baud.\n");
            switch (getchar())
               { case '1': baud(5); baudrate = "300"; break; 
                 case '2': baud(7); baudrate = "1200"; break; 
                 case '3': baud(10); baudrate = "2400"; 
               }
            printf("\nBaud rate set at %s bps.\n", baudrate);
            break;

         case HELP:
            instruct();
            break;

         case CAPTURE:
            BFlag = ~BFlag;
            if (BFlag)
               printf("Capture initiated");
            else
               printf("Capture terminated");
            printf(", %u", BufSiz - BufPtr);
            printf(" bytes free\n");
            break;

         case KEEP:
            if (!BufPtr)
               printf("Nothing to save\n");
            else {
               printf("Save as what file? ");
               scanf("%s", FileName);
               Bufr[BufPtr] = CTRLZ;
               Fd = fopen(FileName,"w");
               if (Fd == NULL)
                 printf("Cannot create %s\n",
                     FileName);
               else {
                  write(Fd, Bufr,
                     (BufPtr/SECSIZ+1)*SECSIZ);
                  fclose(Fd);
                  BFlag = FALSE;
                  BufPtr = 0;
               }
            }
            break;

         case RECEIVE:
            printf("Receive what file? ");
            scanf("%s", FileName);
            readfile(FileName);
            break;

         case SEND:
            printf("Send what file? ");
            scanf("%s", FileName);
            sendfile(FileName);
            break;

         case QUIT:
            hangup();
            break;

         case VIEW:
            View = ~View;
            if (View) {
               printf("View as Ascii or Hex? ");
               ViewMode = toupper(getchar());
               printf("\nDisplay will be in ");
               if (ViewMode == 'A')
                    printf("Ascii\n");
               else
                  printf("Hex\n");
            }
            else
               printf("Viewing disabled\n");
            break;

         case LITERAL:
            while ( !(KbData = bdos(DIRCTIO, INPUT)) );
                  mcharout(KbData);
                  break;

         default:
            mcharout(KbData);
            break;
         }

      if (minprdy()) {
         ModData = mcharinp();
         if (BFlag && (BufPtr < BufSiz))
            Bufr[BufPtr++] = ModData;
         else if   (BFlag)
            printf("Capture Bufr overflow\n");
         putchar(ModData);
      }
   }
}   

instruct()
{
   printf("\n\nLMODEM.C, 5/21/86");
   printf("\nDavid D. Clark, Byte, Nov. '83, pages 410-428");
   printf("\n");
   printf("\nAdapted to C/80 \n");
   printf("Lee R. Bradley, Mouse House Software\n");
   printf("\n");
   printf("LMODEM.C is a small remote communication program.\n");
   printf("When started it acts simply as a dumb terminal. \n");
   printf("The following commands are available:\n\n");

   printf("Press RETURN to continue\n");
   getchar();

   show_char(SETBAUD);
   printf("\t- Set baudrate.  \n");

   show_char(CAPTURE);
   printf("\t- Toggles text capture.  Initially inactive.  When\n");
   printf("\t  acting as a terminal, all text received will be\n");
   printf("\t  saved in a buffer.  The buffer may be saved on disk\n");
   printf("\t  with the 'keep' command below.\n");

   show_char(HELP);
   printf("\t- Help on the commands.  In case you forget what\n");
   printf("\t  you are reading right now, type ctrl-E and you will\n");
   printf("\t  be told what commands do what.\n");

   show_char(KEEP);
   printf("\t- Keep.  Lets you save captured text in a diskfile. \n");
   printf("\t  You will be asked to name the file in which the text\n");
   printf("\t  will be saved.  The text buffer will be cleared if the\n");
   printf("\t  text is saved successfully.\n");

   show_char(RECEIVE);
   printf("\t- Receive file in Ward Christensen protocol.  You will\n");
   printf("\t  be asked for the name of the file to write into.\n");

   show_char(SEND);
   printf("\t- Send file in Ward Christensen protocol.\n");

   show_char(VIEW);
   printf("\t- Toggle data viewing.  Initially inactive.  Data \n");
   printf("\t  transmitted will be displayed in Ascii or Hex.\n");

   show_char(LITERAL);
   printf("\t- Send literal character.  A character typed after this\n");
   printf("\t  character will be sent as is.  In this way, characters\n");
   printf("\t  that represent commands may be sent without being\n");
   printf("\t  interpreted as a command.\n");

   show_char(QUIT);
   printf("\t- Quit.  Exit program.\n\n");

   return;

}
show_char(c)
char c;
{
   if ((c>= 0) && (c<= 31))
      printf("ctrl-%c", c + '@');
   else if (c == ' ')
      printf("<spc>");
   else if ((c >='@') && (c <= '~'))
      putchar(c);
   else if (c == 127)
      printf("<del>");
   else
      printf("%xH", c);
   return;
}

readfile(file)
char *file;
{
   int firstchar, sectnum, sectcurr, sectcomp, errors;
   int checksum;
   int errorflag;
   unsigned j, bufptr;

   if (View) {
      ShowRecv = TRUE;
      ShowTrans = FALSE;
   }

   Fd = fopen(file,"w");
   if (Fd == 0) {
      printf("Cannot create %s\n", file);
      return;
   }
   else
      printf("Receiving %s\n\n", file);
   sectnum = errors = bufptr = 0;
   initializemodem();
   sendchar(NAK);
   do {
      errorflag = FALSE;
      do {                /* get synchronization character */
         firstchar = readchar(5);
      } while (    firstchar != SOH
                && firstchar != EOT
                && firstchar != TIMEOUT);
      if (firstchar == TIMEOUT) {
         errorflag = TRUE;
         printf("Timeout error\n");
      }
      if (firstchar == SOH) {        /* main if */
         sectcurr = readchar(1);
         sectcomp = readchar(1);
         if ((sectcurr + sectcomp) == -1) {
            if ((sectcurr & 0xff) == (sectnum + 1 & 0xff)) { /* lrb */
               checksum = 0;
               if (ViewMode == 'A')
                  AsciiFlg = TRUE;
               for (j = bufptr; j < (bufptr + SECSIZ); j++) {
                    Bufr[j] = readchar(1);
                    checksum = (checksum + Bufr[j]) & 0xff;
               }
               AsciiFlg = FALSE;
               if ((checksum & 0xff) == (readchar(1) & 0xff)) {	/* lrb */
                  errors = 0;
                  sectnum++;
                  bufptr += SECSIZ;
                  if ((sectnum % BUFSECS) == 0) {
                     bufptr = 0;
                     if (write(Fd, Bufr, BLKSIZ) == NULL) {
                        printf("Error writing file\n");
                        fclose(Fd);
                        return;
                     }
                     do ; 
                     while (readchar(1) != TIMEOUT);
                  }
                  if (!ShowRecv)
                     if (((sectnum - 1 ) % DOTS) ==0)
                        printf("\n<%4d>.", sectnum);
                     else
                        printf(".");
                  sendchar(ACK);
               }
               else {
                  printf("\nChecksum error, expected ");
                  printf("<%0x>\n", checksum);
                  errorflag = TRUE;
               }	/*  This is a comment  */
           }   
           else 
              if ((sectcurr & 0xff) == (sectnum & 0xff)) {  /* lrb */ 
                 printf("\nReceived duplicate sector %d\n", sectnum);
                 /* wait for silence on the line */
                 do ;  /* nothing */
                 while (readchar(1) != TIMEOUT);
                 sendchar(ACK);
              }
              else {
                 printf("\nSynchronization error\n");
                 errorflag = TRUE;
              }
          }
          else {
             printf("\nSector number error\n");
             errorflag = TRUE;
          }
      }  /* end of main if.   This right brace was missing from Byte */ 

      if (errorflag == TRUE) {
         errors++;
         printf("Error %d\n", errors);
         while (readchar(1) != TIMEOUT);
         sendchar(NAK);
      }

   }  while (firstchar != EOT && errors != ERRORMAX);
    
   if ((firstchar == EOT) && (errors < ERRORMAX)) {
      sendchar(ACK);
      write(Fd, Bufr, bufptr);
      fclose(Fd);
      printf("\n\nTransfer complete\n");
   }
   else
      printf("\n\nAborting\n");
}


readchar (seconds)
unsigned seconds;
{
   char data;
   seconds = seconds*SPS;
   while (!minprdy() && seconds)  /* wait until input ready */
      --seconds;
   if (!seconds)
      return(TIMEOUT);            /* nothing arrived in time */
   data = mcharinp();             /* get it */
   if (ShowRecv) {                /* show if needed */
      if (AsciiFlg)
         if (((data >= ' ') && (data <= DATAMASK))
             || data == LF || data == CR || data == TAB)
            putchar(data);
         else
            printf("[%0x]", data);
      else
         printf("[%0x]", data);
   }
   return data;
}

sendfile(file)
char *file;
{
   int sectnum, sectors, attempts;
   int checksum;
   unsigned j, bufptr;

   if (View) {
      ShowRecv = FALSE;
      ShowTrans = TRUE;
   }
   Fd = fopen(file, "r");
   if (Fd == NULL) {
      printf("Cannot open %s\n", file);
      return;
   }
   else
      printf("Sending %s\n\n", file);
   initializemodem();
   attempts = 0;
   sectnum = 1;

   while ((sectors = read(Fd, Bufr, BufSiz)) && (attempts != RETRYMAX)) {
      if (sectors == NULL) {
         printf("\nError reading file\n");
         fclose(Fd);
         return;
      }
      else {
         sectors = sectors / SECSIZ + 1;
         bufptr = 0;
         do {
            attempts = 0;
            do {
               if (!ShowTrans)
                  if (((sectnum - 1) % DOTS) == 0)
                     printf("\n<%4d>.", sectnum);
                  else
                     printf(".");
               sendchar(SOH);
               sendchar(sectnum);
               sendchar(~sectnum);
               checksum = 0;
               if (ViewMode = 'A')
                  AsciiFlg = TRUE;
               for (j= bufptr; j < (bufptr + SECSIZ); j++) {
                  sendchar(Bufr[j]);
                  checksum = (checksum + Bufr[j]) & 0xff;
               }
               AsciiFlg = FALSE;
               sendchar(checksum);
               purgeline();
               attempts++;
            } while ((readchar(10) != ACK) && 
                      (attempts != RETRYMAX));
            bufptr += SECSIZ;
            sectnum++;
            sectors--;
         } while (sectors && (attempts != RETRYMAX));
      }
   }

   if (attempts == RETRYMAX)
      printf("\nNo acknowledgement of sector, aborting\n");
   else {
      attempts = 0;
      do {
         sendchar(EOT);
         purgeline();
         attempts++;
      } while ((readchar(10) != ACK) && (attempts != RETRYMAX));
      if (attempts == RETRYMAX) {
         printf("\nNo acknowledgement of end of file");
         printf(", aborting\n");
      }
      else 
         printf("\nTransfer complete\n");
   }
   fclose(Fd);
   return;
}

sendchar(data)
char data;
{
   while (!moutrdy());           /* wait until output ready */
   
   mcharout(data);               /* send it */
 
   if (ShowTrans) {              /* show it, if needed */
      if (AsciiFlg)
         if (((data >= ' ') && (data <= DATAMASK))
             || data == LF || data == CR || data == TAB)
            putchar(data);
         else
            printf("[%0x]", data);
      else
         printf("[%0x]", data);
   }
   return;
}

/* Hardware dependent code */

#define     BAUDRATE    0
#define     DATAPORT    4
#define     STATUSPORT  6
#define     DAV   1
#define     TBE   4

initializemodem()
{
   purgeline();               /* minimal code */
}

purgeline()
{
   while (minprdy())           /* while there are characters ... */
      mcharinp();                   /* gobble them */
}

mcharinp()
{
   char c;

   while (!minprdy());   /* do nothing until ready */
   c = rx();             /* get data  */
   return c;
}

mcharout(c)
char c;
{
   while (!moutrdy());    /* do nothing until ready */
   tx(c);                 /* put data */
}

int rx()
{
#asm
   IN  DATAPORT           ; get data from port
   MVI  H,0
   MOV  L,A   
#endasm
}
   
minprdy()
{                         /* returns 0 or 1 if (un)ready */
#asm
   IN  STATUSPORT
   ANI  DAV
   MVI  H,0
   MOV  L,A    
   RZ
   MVI  L,1 
#endasm
}

moutrdy()
{                         /* returns 0 of 1 if (un)ready */
#asm
   IN  STATUSPORT
   ANI  TBE
   MVI  H,0
   MOV  L,A
   RZ
   MVI  L,1
#endasm
}

int tx(c)
char c;
{
#asm
   POP  H
   POP  D
   PUSH  D
   PUSH  H
   MOV   A,E
   OUT   DATAPORT
#endasm
}

int baud(c)
char c;
{
#asm
   POP  H
   POP  D
   PUSH  D
   PUSH  H
   MOV   A,E
   OUT   BAUDRATE
#endasm
}
ctsready()
{
   return TRUE;
}

hangup()
{
   return TRUE;           /* nothing to do */
}

#include "stdlib.c"

}
ctsready()
{
   return TRUE;
}

hangup()
{
   return TRUE;           /* noth